/*
 *
 * @package MoltX
 * @author $Author$
 * @version $Id$
 * @link $URL$
 * @copyright Copyright (c) 2005-2010, Matthew Oltendorf
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
 *
 */

var
	/*
	 * Master class
	 * @package Blink
	 *
	 * Singleton
	 */
	blink,
	_blink; // Debugging only.

/*
 * @ignore
 */
( function ( $ )
{
var
	/*
	 * Object construction class
	 * @package MoltX
	 *
	 * Creates objects.
	 *
	 * Singleton
	 */
	object = {
		// Object storage.
		index: [ ],
		storage: [ ],

		/*
		 * Destroy object function
		 *
		 * @return null
		 */
		destroy: function ( name )
		{
			// Declare variables.
			var id;

			id = $.inArray ( name, this.index );

			if ( id === -1 )
			{
				return null;
			}

			this.index [ id ] = null;
			this.storage [ id ] = null;

			delete this.index [ id ];
			delete this.storage [ id ];

			return null;
		},

		/*
		 * Get object function
		 *
		 * @return object
		 */
		get: function ( name, factory, instance )
		{
			// Declare variables.
			var id;

			id = $.inArray ( name, this.index );

			if ( id === -1 )
			{
				// Dynamically generate a new object.
				return this.make ( name, factory, null );
			}

			// Return the object.
			return this.storage [ id ];
		},

		/*
		 * Make object function
		 *
		 * @return object
		 */
		make: function ( name, factory, prototype )
		{
			// Declare variables.
			var i, id, object;

			id = name ? $.inArray ( name, this.index ) : -1;

			if ( id === -1 )
			{
				if ( factory )
				{
					/*
					 * Object constructor function
					 *
					 * @return mixed
					 */
					object = function ( )
					{
						if ( this.initiate )
						{
							// Call the initiate function. If it is not present, it will crash.
							this.initiate.apply ( this, arguments );
						}

						return null;
					};
				}
				else
				{
					/*
					 * @ignore
					 */
					object = { };
				}
			}
			else
			{
				/*
				 * @ignore
				 */
				object = this.storage [ id ];
			}

			if ( prototype )
			{
				if ( factory )
				{
					for ( i in object.prototype )
					{
						delete object.prototype [ i ];
					}

					// Prototype it!
					for ( i in prototype )
					{
						object.prototype [ i ] = prototype [ i ];
					}
				}
				else
				{
					for ( i in object )
					{
						delete object [ i ];
					}

					// Prototype it!
					for ( i in prototype )
					{
						object [ i ] = prototype [ i ];
					}
				}
			}

			if ( id === -1 && name )
			{
				// Store the object.
				this.index.push ( name );
				this.storage.push ( object );
			}

			// Return the object.
			return object;
		}
	},

	/*
	 * Method construction class
	 * @package MoltX
	 *
	 * Creates methods.
	 *
	 * Singleton
	 */
	method = {
		// Method storage.
		index: [ ],
		instances: [ ],
		storage: [ ],

		/*
		 * Destroy method function
		 *
		 * @return null
		 */
		destroy: function ( name )
		{
			// Declare variables.
			var id, instance;

			id = $.inArray ( name, this.index );

			if ( id === -1 )
			{
				// Assume it doesn't exist.
				return null;
			}

			instance = this.instances [ id ];

			// Set the object to inactive, and call the closure.
			instance.active = false;
			instance.initiate ( );

			this.index [ id ] = null;
			this.storage [ id ] = null;
			this.instances [ id ] = null;

			delete this.index [ id ];
			delete this.storage [ id ];
			delete this.instances [ id ];
			delete instance;

			return null;
		},

		/*
		 * Get method function
		 *
		 * @return object
		 */
		get: function ( name )
		{
			// Declare variables.
			var id;

			id = $.inArray ( name, this.index );

			if ( id === -1 )
			{
				// Dynamically generate a new object.
				return this.make ( name, null );
			}

			// Return the method.
			return this.instances [ id ].initiate;
		},

		/*
		 * Make method function
		 *
		 * @return object
		 */
		make: function ( name, method )
		{
			// Declare variables.
			var id, instance, object;

			id = name ? $.inArray ( name, this.index ) : -1;

			if ( id === -1 )
			{
				/*
				 * Method constructor class
				 * @package MoltX
				 *
				 * Factory
				 */
				object = function ( )
				{
					this.active = true;

					return null;
				};

				// Create a new instance of the object.
				instance = new object ( );

				/*
				 * Method initiation function
				 *
				 * @return mixed
				 */
				object.prototype.initiate = ( function ( instance )
				{
					return function ( )
					{
						if ( ! instance )
						{
							return null;
						}

						if ( instance.active )
						{
							// Call the initiate function. If it is not present, it will crash.
							return instance.method.apply ( this, arguments );
						}

						// Delete the instance, hopefully freeing it from memory.
						instance = null;
						delete instance;

						return null;
					};
				} ) ( instance );
			}
			else
			{
				/*
				 * @ignore
				 */
				object = this.storage [ id ];
				instance = this.instances [ id ];
			}

			if ( method )
			{
				// Prototype it!
				object.prototype.method = method;
			}

			if ( id === -1 && name )
			{
				// Store the object.
				this.index.push ( name );
				this.storage.push ( object );
				this.instances.push ( instance );
			}

			// Return the method.
			return instance.initiate;
		}
	},

	/*
	 * Constants class
	 * @package MoltX
	 *
	 * Creates methods.
	 *
	 * Singleton
	 */
	C = {
		ALL: 0,
		BOTTOM: 2,
		TOP: 1
	};

var
	/*
	 * Internal class
	 * @package Blink
	 *
	 * Singleton
	 */
	internal = object.make ( 'internal', false, {
		decrease: .5,
		formatting: true,
		handlers: [ ],
		locked: false,
		offline: 30000,
		scrolling: 50,
		sending: 500,
		timers: [ ],

		/*
		 * Data class
		 * @package Blink
		 *
		 * Factory
		 */
		data: object.make ( 'data', true, {
			data: undefined,
			external: undefined, // Defined in initiate.
			original: undefined,

			bind: method.get ( 'bind' ),
			log: method.get ( 'log' ),
			trigger: method.get ( 'trigger' ),
			unbind: method.get ( 'unbind' ),

			/*
			 * Change function
			 *
			 * @return null
			 */
			change: function ( value )
			{
				this.external.data = this.data = value;

				this.trigger ( 'change', [ this ] );

				return null;
			},

			/*
			 * Decrease function
			 *
			 * @return null
			 */
			decrease: function ( amount )
			{
				if ( ! amount )
				{
					this.external.data = --this.data;

					return null;
				}

				this.external.data = this.data -= amount;

				this.trigger ( 'change', [ this ] );

				return null;
			},

			/*
			 * Increase function
			 *
			 * @return null
			 */
			increase: function ( amount )
			{
				if ( ! amount )
				{
					this.external.data = ++this.data;

					return null;
				}

				this.external.data = this.data += amount;

				this.trigger ( 'change', [ this ] );

				return null;
			},

			/*
			 * Constructor function
			 *
			 * @return null
			 */
			initiate: function ( value )
			{
				// Define reference variables.
				this.external = {
					data: undefined
				};

				this.change ( value );

				this.original = this.data;

				return null;
			}
		} ),

		/*
		 * Handler class
		 * @package Blink
		 *
		 * Factory
		 */
		handler: object.make ( null, true, {
			cache: false,
			comet: false,
			channels: undefined, // Defined in initiate.
			delay: 1250,
			errors: 0,
			force: false,
			identifier: -1,
			instances: undefined, // Defined in initiate.
			immediate: false,
			messages: undefined, // Defined in initiate.
			receive: '',
			receiving: false,
			send: undefined,
			timers: undefined, // Defined in initiate.
			user: -1,

			data: object.get ( 'data', true, false ),
			blink: object.get ( 'internal', false, false ),
			closure: method.get ( 'closure' ),
			instance: object.get ( 'instance', true, false ),
			log: method.get ( 'log' ),
			timer: object.get ( 'timer', true, false ),

			/*
			 * AJAX request class
			 * @package Blink
			 *
			 * Factory
			 */
			ajax: object.make ( null, true, {
				cache: false,
				data: undefined,
				dataType: 'text',
				global: false,
				handler: undefined,
				send: false,
				timeout: 30000,
				type: 'POST',
				url: '',

				log: method.get ( 'log' ),

				reset: undefined,

				/*
				 * Data generation function
				 *
				 * @return null
				 */
				build: function ( )
				{
					// Declare variables.
					var data, i, j, k, l, m, n, o;

					// Whip up some JSON.
					data = [ ];
					o = 0;

					for ( i = 0; i < this.handler.instances.length; ++i )
					{
						j = [ ];
						k = this.handler.instances [ i ];

						// Ensure query data is up to date.
						if ( k.action > k.build.action )
						{
							k.build.action = k.action;
						}

						if ( k.level > k.build.level )
						{
							k.build.level = k.level;
						}

						if ( k.line > k.build.line )
						{
							k.build.line = k.line;
						}

						while ( k.build.messages.length )
						{
							l = k.build.messages.shift ( );
							m = l.content;

							if ( m != '' && typeof ( m ) === 'string' )
							{
								m = m.replace ( /([\\"])/g, '\\$1' );
								n = this.handler.messages.free.length ? this.handler.messages.free.shift ( ) : this.handler.messages.current.length;

								l.transaction = n;

								if ( n === this.handler.messages.current.length )
								{
									this.handler.messages.current.push ( l );
								}
								else
								{
									this.handler.messages.current [ n ] = l;
								}

								j.push ( '{"content":"' + m + '","transaction":' + n + '}' );

								this.handler.cache = false;
							}
							else
							{
								m = '';
							}
						}

						if ( ! j.length )
						{
							data.push ( '{"action":' + k.build.action + ',"channel":' + k.channel + ', "level":' + k.build.level + ',"line":' + k.build.line + '}' );
						}
						else
						{
							data.push ( '{"action":' + k.build.action + ',"channel":' + k.channel + ', "level":' + k.build.level + ',"line":' + k.build.line + ',"messages":[' + j.join ( ',' ) + ']}' );

							o += j.length;

							this.log ( 'sent ', j.length, ' messages in instance #', k.identifier.data );
						}
					}

					this.data = "data=" + encodeURIComponent ( "[" + data.join ( ',' ) + "]" );

					if ( o > 0 )
					{
						this.log ( 'sent ', o, ' messages in handler #', this.handler.identifier.data );
					}

					return true;
				},

				/*
				 * Error function
				 *
				 * @return null
				 */
				error: function ( document, status ) // Use anonymous function to prevent it from screwing up "this."
				{
					// Increment error count.
					++this.handler.errors;

					this.handler.force = false;
					this.handler.receiving = false;

					this.log ( 'unsuccessful query in handler #', this.handler.identifier.data );
					this.log ( document );

					// We always use the que on an error, otherwise we would DoS the server.
					this.handler.que ( );

					return true;
				},

				/*
				 * Constructor function
				 *
				 * @return null
				 */
				initiate: function ( handler )
				{
					this.handler = handler;
					this.url = this.handler.receive;

					this.build ( );

					return null;
				},

				/*
				 * Success function
				 *
				 * @return null
				 */
				success: function ( document, status ) // Use anonymous function to prevent it from screwing up "this."
				{
					// Declare variables.
					var i, index, user;

					try
					{
						eval ( 'document = ' + document + ';' );
					}
					catch ( exception )
					{
						this.error ( document, status );

						return null;
					}

					// Reset error count.
					this.handler.errors = 0;

					this.handler.force = false;
					this.handler.receiving = false;

					this.log ( 'successful query in handler #', this.handler.identifier.data );

					if ( document )
					{
						if ( document.settings )
						{
							this.log ( 'received ', document.settings.length, ' settings in handler #', this.handler.identifier.data );
						}

						if ( document.users )
						{
							this.log ( 'received ', document.users.length, ' users in handler #', this.handler.identifier.data );

							for ( i = 0; i < document.users.length; ++i )
							{
								user = this.handler.users.get ( document.users [ i ].identifier );

								// Manipulate users.
								user.update ( document.users [ i ] );
							}
						}

						if ( document.channels )
						{
							this.log ( 'received ', document.channels.length, ' channels in handler #', this.handler.identifier.data );

							for ( i = 0; i < document.channels.length; ++i )
							{
								index = $.inArray ( document.channels [ i ].channel, this.handler.channels );

								if ( index === -1 )
								{
									// For now, we don't have the window spawner. Skip.
									continue;
								}

								this.handler.instances [ index ].response ( document.channels [ i ] );
							}
						}
					}

					if ( ! this.handler.comet )
					{
						this.handler.que ( ); // Discard arguments.
					}
					else
					{
						this.handler.retrieve ( ); // Discard arguments.
					}

					return true;
				}
			} ),

			/*
			 * Global users class
			 * @package Blink
			 *
			 * Singleton
			 */
			users: object.make ( null, true, {
				handler: undefined,
				identifiers: undefined, // Defined in initiate.
				storage: undefined, // Defined in initiate.

				/*
				 * Global user class
				 * @package Blink
				 *
				 * Singleton
				 */
				user: object.make ( null, true, {
					colors: undefined, // Defined in initiate.
					display: '',
					element: undefined,
					global: undefined,
					handler: undefined,
					identifier: -1,
					local: undefined, // Defined in initiate.
					server: -1,
					status: 0,

					format: method.get ( 'format' ),
					language: object.get ( 'language', false ),
					log: method.get ( 'log' ),

					/*
					 * Close user function
					 *
					 * @return null
					 */
					close: function ( )
					{
						// Declare variables.
						var i;

						for ( i = this.local.length - 1; i >= 0; --i )
						{
							this.local [ i ].close ( );
						}

						this.global.storage.splice ( this.identifier, 1 );

						for ( i = this.identifier; i < this.global.storage.length; ++i )
						{
							--this.global.storage [ i ].identifier;
						}

						this.log ( 'destroyed global user #', this.identifier, ' in handler #', this.handler.identifier.data );

						// Clear references.
						this.global = undefined;
						this.handler = undefined;

						this.identifier = -1;

						return null;
					},

					/*
					 * Constructor function
					 *
					 * Might be a dud function; delete later if no use is found.
					 *
					 * @return null
					 */
					color: function ( color )
					{
						// Declare variables.
						var i, j, value, values;

						// Validate color.

						color = color.split ( ',' );
						values = [ ];

						for ( i = 0; i < 3; ++i )
						{
							if ( color [ i ] !== undefined )
							{
								value = parseInt ( color [ i ] );

								if ( value < 0 )
								{
									value = 0;
								}
								else if ( value > 255 )
								{
									value = 255;
								}
							}
							else
							{
								value = 0;
							}

							values.push ( value );
						}

						color = 'rgb(' + values.join ( ',' ) + ')';

						if ( color === this.colors.current )
						{
							return null;
						}

						for ( i = 0; i < this.local.length; ++i )
						{
							for ( j = 0; j < this.local [ i ].messages.length; ++j )
							{
								$ ( this.local [ i ].messages [ j ].elements.user ).animate (
								{
									color: color
								},
								{
									duration: 2500
								} );

								if ( this.local [ i ].messages [ j ].elements.replace && this.local [ i ].messages [ j ].elements.replace.user )
								{
									$ ( this.local [ i ].messages [ j ].elements.replace.user ).css ( 'color', color );
								}
							}
						}

						this.colors.current = color;

						return null;
					},

					/*
					 * Generate function
					 *
					 * @return null
					 */
					generate: function ( )
					{
						// Declare variables.
						var user;

						user = document.createElement ( 'span' );
						$ ( user ).addClass ( 'list' );
						$ ( user ).addClass ( 'user' );
						$ ( user ).addClass ( 'u' + this.server );

						$ ( user ).append ( this.list );

						if ( this.colors.current !== undefined )
						{
							$ ( user ).css ( 'color', this.colors.current );
						}

						this.element = user;

						return null;
					},

					/*
					 * Constructor function
					 *
					 * @return null
					 */
					initiate: function ( handler, user )
					{
						// Define reference variables.
						this.colors = {
							current: undefined,
							original: undefined,
							received: undefined
						};

						// Create a reference to our parent.
						this.handler = handler;
						this.global = this.handler.users;
						this.local = [ ];

						this.identifier = this.global.storage.length;
						this.server = user;

						this.log ( 'created global user #', this.identifier, ' in handler #', this.handler.identifier.data );

						this.global.identifiers.push ( user );
						this.global.storage.push ( this );

						if ( this.display )
						{
							this.list = this.format ( this.language.format.user.list.standard, this.display, this.server );
						}
						else
						{
							this.list = this.format ( this.language.format.user.list.unknown, this.display, this.server );
						}

						this.generate ( );

						return null;
					},

					/*
					 * Update user function
					 *
					 * @return null
					 */
					update: function ( data )
					{
						// Declare variables.
						var color, element, i, j, value, values;

						if ( data.status !== this.status )
						{
							if ( this.status && data.status && typeof ( data.status ) === 'number' )
							{
								for ( i = 0; i < this.local.length; ++i )
								{
									$ ( this.local [ i ].element ).removeClass ( 's' + this.status );
									$ ( this.local [ i ].element ).addClass ( 's' + data.status );
								}

								this.status = data.status;
							}
							else if ( ! this.status && data.status && typeof ( data.status ) === 'number' )
							{
								for ( i = 0; i < this.local.length; ++i )
								{
									$ ( this.local [ i ].element ).removeClass ( 's' + this.status );
									$ ( this.local [ i ].element ).addClass ( 's' + data.status );

									this.local [ i ].show ( );
								}

								this.status = data.status;
							}
							else if ( this.status > 0 && ! data.status )
							{
								for ( i = 0; i < this.local.length; ++i )
								{
									$ ( this.local [ i ].element ).removeClass ( 's' + this.status );
									$ ( this.local [ i ].element ).addClass ( 's' + data.status );

									this.local [ i ].hide ( );
								}

								this.status = 0;
							}
						}

						if ( data.color !== this.colors.received && typeof ( data.color ) === 'string' )
						{
							this.colors.received = data.color;

							// Validate color.

							color = data.color.split ( ',' );
							values = [ ];

							for ( i = 0; i < 3; ++i )
							{
								if ( color [ i ] !== undefined )
								{
									value = parseInt ( color [ i ] );

									if ( isNaN ( value ) || value < 0 )
									{
										value = 0;
									}
									else if ( value > 255 )
									{
										value = 255;
									}
								}
								else
								{
									value = 0;
								}

								values.push ( value );
							}

							color = 'rgb(' + values.join ( ',' ) + ')';

							if ( color !== this.colors.current )
							{
								this.colors.current = color;
							}
							else
							{
								color = false;
							}
						}

						if ( typeof ( data.display ) === 'string' && data.display !== this.display )
						{
							this.display = data.display;

							if ( this.display )
							{
								this.list = this.format ( this.language.format.user.list.standard, this.display, this.server );
							}
							else
							{
								this.list = this.format ( this.language.format.user.list.unknown, this.display, this.server );
							}

							this.generate ( );

							for ( i = 0; i < this.local.length; ++i )
							{
								this.local [ i ].replace ( );

								for ( j = 0; j < this.local [ i ].messages.length; ++j )
								{
									element = this.local [ i ].messages [ j ].generate ( 'user' );

									if ( this.local [ i ].messages [ j ].instance.animations )
									{
										if ( ! this.local [ i ].messages [ j ].elements.replace )
										{
											this.local [ i ].messages [ j ].elements.replace = {
												container: undefined,
												content: undefined,
												time: undefined,
												user: undefined
											};
										}

										if ( ! this.local [ i ].messages [ j ].elements.replace.user )
										{
											this.local [ i ].messages [ j ].elements.replace.user = element;
											this.local [ i ].messages [ j ].replace ( 'user' );
										}
										else if ( this.local [ i ].messages [ j ].elements.replace.container )
										{
											$ ( this.local [ i ].messages [ j ].elements.replace.user ).replaceWith ( element );
											this.local [ i ].messages [ j ].elements.replace.user = element;
										}
										else
										{
											this.local [ i ].messages [ j ].elements.replace.user = element;
										}
									}
									else
									{
										$ ( this.local [ i ].messages [ j ].elements.user ).replaceWith ( element );
										this.local [ i ].messages [ j ].elements.user = element;
									}
								}
							}

							// Disable color animation. No need.
							color = undefined;
						}

						if ( color )
						{
							$ ( this.element ).css ( 'color', color );

							for ( i = 0; i < this.local.length; ++i )
							{
								if ( this.local [ i ].instance.animations )
								{
									$ ( this.local [ i ].element ).animate (
									{
										color: color
									},
									{
										duration: 2500
									} );
								}
								else
								{
									$ ( this.element ).css ( 'color', color );
								}

								for ( j = 0; j < this.local [ i ].messages.length; ++j )
								{
									if ( this.local [ i ].instance.animations )
									{
										$ ( this.local [ i ].messages [ j ].elements.user ).animate (
										{
											color: color
										},
										{
											duration: 2500
										} );
									}
									else
									{
										$ ( this.local [ i ].messages [ j ].elements.user ).css ( 'color', color );
									}

									if ( this.local [ i ].messages [ j ].elements.replace && this.local [ i ].messages [ j ].elements.replace.user )
									{
										$ ( this.local [ i ].messages [ j ].elements.replace.user ).css ( 'color', color );
									}
								}
							}
						}

						return null;
					}
				} ),

				/*
				 * Close users function
				 *
				 * @return null
				 */
				close: function ( )
				{
					// Declare variables.
					var i;

					for ( i = this.storage.length - 1; i >= 0; --i )
					{
						this.storage [ i ].close ( );
					}

					// Clear references.
					this.handler = undefined;

					return null;
				},

				/*
				 * Get user function
				 *
				 * @return null
				 */
				get: function ( user )
				{
					// Declare variables.
					var index;

					index = $.inArray ( user, this.identifiers );

					if ( index === -1 )
					{
						// Create new user.
						return new this.user ( this.handler, user );
					}

					return this.storage [ index ];
				},

				/*
				 * Constructor function
				 *
				 * @return null
				 */
				initiate: function ( handler )
				{
					// Define reference variables.
					this.identifiers = [ ];
					this.storage = [ ];

					this.handler = handler;

					return null;
				}
			} ),

			/*
			 * Run action function
			 *
			 * @return boolean
			 */
			action: function ( action )
			{
				switch ( action )
				{
					case 'close':
						this.close ( );
					break;

					case 'comet':
						// Enable comet.
						this.comet = true;
					break;

					case 'pull':
						// Disable comet.
						this.comet = false;
					break;
				}

				return false;
			},

			/*
			 * Change data function
			 *
			 * @return boolean
			 */
			change: function ( data )
			{
				this.log ( 'handler #', this.identifier.original, ' changed to handler #', this.identifier.data );

				this.identifier.original = this.identifier.data;

				return null;
			},

			/*
			 * Destructor function
			 *
			 * @return null
			 */
			close: function ( )
			{
				// Declare variables.
				var i;

				if ( this.timers.retrieve )
				{
					this.timers.retrieve.destroy ( );
					this.timers.retrieve = undefined;
				}

				// Close instances.
				for ( i = this.instances.length - 1; i >= 0; --i )
				{
					this.instances [ i ].close ( );
				}

				this.users.close ( );

				// Remove the reference to self.
				this.blink.handlers.splice ( this.identifier.data, 1 );

				for ( i = this.identifier.data; i < this.blink.handlers.length; ++i )
				{
					this.blink.handlers [ i ].identifier.decrease ( );
				}

				this.log ( 'destroyed handler #', this.identifier.data );

				this.identifier.unbind ( );
				this.identifier.change ( -1 );
			},

			/*
			 * Constructor function
			 *
			 * @return null
			 */
			initiate: function ( receive, user, send, delay )
			{
				// Define reference variables.
				this.channels = [ ];
				this.instances = [ ];
				this.messages = {
					current: [ ],
					free: [ ]
				};
				this.users = new this.users ( this );

				// Define timers.
				this.timers = {
					retrieve: new this.timer ( 0, true ),
					sending: new this.timer ( 0, true )
				};

				this.timers.retrieve.bind ( 'run', this.closure ( this.retrieve, [ ] ) );
				this.timers.sending.bind ( 'run', this.closure ( this.reset, [ true ] ) );

				this.identifier = new this.data ( this.blink.handlers.length );
				this.identifier.bind ( 'change', this.closure ( this.change, [ ] ) );
				this.blink.handlers.push ( this );

				this.log ( 'handler #', this.identifier.data, ' created' );

				this.receive = receive;
				this.send = send;
				this.user = user;

				if ( typeof ( delay ) === 'number' )
				{
					this.delay = delay;
				}

				this.ajax = new this.ajax ( this );

				// Start looping retrieval of messages.
				var timer = new this.timer ( 100 );
				timer.bind ( 'run', this.closure ( this.retrieve, [ ] ) );

				return null;
			},

			/*
			 * Retrieve data function
			 *
			 * @return null
			 */
			que: function ( )
			{
				this.log ( 'called que in handler #', this.identifier.data );

				if ( this.timers.retrieve.active )
				{
					return null;
				}

				// Reset the timeout.
				this.timers.retrieve.reset ( Math.round ( this.blink.decrease * this.delay * ( this.errors + 1 ) ) );

				return null;
			},

			/*
			 * Reset function
			 *
			 * Forces an existing connection to terminate early, then retrieves
			 * data from the server again; thus flushing any pending messages
			 * from the cache and sending them to the server. Only used in comet
			 * mode.
			 *
			 * @param boolean force whether or not to bypass the timer check.
			 *
			 * @return null
			 */
			reset: function ( force )
			{
				// First, is the timer running?
				if ( this.timers.sending.active && ! force )
				{
					return null;
				}

				// Second, check if there's an existing connection.
				if ( this.receiving )
				{
					// Break existing connection.
					this.ajax.reset ( );
				}

				// Third, retrieve the data (again).
				this.retrieve ( true );

				return null;
			},

			/*
			 * Retrieve data function
			 *
			 * @return null
			 */
			retrieve: function ( force )
			{
				// Declare variables.
				var data;

				this.log ( 'retrieve called in handler #', this.identifier.data );

				if ( ( this.receiving && ! force ) || this.force )
				{
					return false;
				}

				this.receiving = true;

				if ( force )
				{
					this.force = true;
				}

				this.ajax.build ( );
				this.ajax.reset = $.ajax ( this.ajax ).close;

				if ( this.comet && ! this.timers.sending.active )
				{
					this.timers.sending.reset ( this.blink.sending );
				}

				return null;
			}
		} ),

		/*
		 * Instance class
		 * @package Blink
		 *
		 * Factory
		 */
		instance: object.make ( 'instance', true, {
			animations: true, // Set to false when we have the startup system completed.
			build: undefined, // Defined in initiate.
			channel: '',
			elements: undefined,
			handler: undefined,
			identifier: -1,
			level: -1,
			line: -1,
			name: 'blink',

			blink: object.get ( 'internal', false ),
			closure: method.get ( 'closure' ),
			data: object.get ( 'data', true, false ),
			log: method.get ( 'log' ),

			/*
			 * Messages class
			 * @package Blink
			 *
			 * Singleton
			 */
			messages: object.make ( null, true, {
				active: false,
				append: undefined, // Defined in initiate.
				instance: undefined,
				levels: undefined, // Defined in initiate.
				storage: undefined, // Defined in initiate.
				types: undefined, // Defined in initiate.

				/*
				 * Message class
				 * @package Blink
				 *
				 * Factory
				 */
				message: object.make ( null, true, {
					content: '',
					elements: undefined, // Defined in initiate.
					identifier: -1,
					identifiers: undefined, // Defined in initiate.
					instance: undefined,
					level: -1,
					line: -1,
					local: undefined,
					power: 0,
					type: 0,
					user: undefined,
					visible: true,

					blink: object.get ( 'internal', false ),
					closure: method.get ( 'closure' ),
					format: method.get ( 'format' ),
					language: object.get ( 'language', false ),
					log: method.get ( 'log' ),
					time: method.get ( 'time' ),

					/*
					 * Append message function
					 *
					 * @return null
					 */
					append: function ( sibling )
					{
						if ( this.instance.elements.container )
						{
							if ( sibling )
							{
								this.instance.scrolling.get ( );

								$ ( sibling.elements.container ).before ( this.elements.container );

								this.instance.scrolling.set ( );
							}
							else
							{
								$ ( this.instance.elements.container ).append ( this.elements.container );
							}
						}

						return null;
					},

					/*
					 * Close message function
					 *
					 * @return null
					 */
					close: function ( )
					{
						// Declare variables.
						var i;

						this.content = '';

						this.local.storage.splice ( this.identifier, 1 );

						for ( i = this.identifier; i < this.local.storage.length; ++i )
						{
							--this.local.storage [ i ].identifier;
						}

						if ( this.level !== -1 )
						{
							this.local.levels [ this.level ] [ this.line ] = null;
							delete this.local.levels [ this.level ] [ this.line ];
							this.level = -1;
							this.line = -1;
						}

						this.local.types [ this.type ].splice ( this.identifiers.type, 1 );

						for ( i = this.identifiers.type; i < this.local.types [ this.type ].length; ++i )
						{
							--this.local.types [ this.type ] [ i ].identifiers.type;
						}

						this.type = 0;

						if ( this.identifiers.user !== -1 )
						{
							this.user.messages.splice ( this.identifiers.user, 1 );

							for ( i = this.identifiers.user; i < this.user.messages.length; ++i )
							{
								--this.user.messages [ i ].identifiers.user;
							}

							this.user = undefined;
						}

						this.log ( 'destroyed message #', this.identifier, ' in instance #', this.instance.identifier.data );

						// Clear references.
						this.instance = undefined;
						this.local = undefined;

						this.elements = {
							container: undefined,
							message: undefined,
							resplace: undefined,
							time: undefined,
							user: undefined
						};

						this.identifier = -1;

						this.identifiers = {
							type: -1,
							user: -1
						};

						return null;
					},

					/*
					 * Generate function
					 *
					 * @return null
					 */
					generate: function ( mode, content, time, user )
					{
						// Declare variables.
						var clear, container, date, difference, element, elements, external, format, i, input, internal, parameters;

						//if ( ! mode )
						//{
						//	elements = {
						//		content: undefined,
						//		time: undefined,
						//		user: undefined
						//	};
						//}

						if ( ! mode || mode === 'content' )
						{
							if ( this.language.format.content [ this.type ] !== undefined )
							{
								format = this.language.format.content [ this.type ];
							}
							else
							{
								format = this.language.format.content [ 0 ];
							}

							content = document.createElement ( 'span' );
							$ ( content ).addClass ( this.instance.name );
							$ ( content ).addClass ( 'message' );
							$ ( content ).addClass ( 'content' );

							if ( this.blink.formatting )
							{
								$ ( content ).append ( this.format ( format, this.content ) );
							}
							else
							{
								// @todo: Better formatting stripping method for text.
								element = document.createElement ( 'span' );
								$ ( element ).append ( this.content );

								// Get just the text (no HTML).
								$ ( content ).append ( this.format ( format, $ ( element ).text ( ) ) );
							}

							if ( mode )
							{
								return content;
							}

							//external = $ ( document.createElement ( 'span' ) ).append ( content );
							//$ ( external ).addClass ( this.instance.name );
							//$ ( external ).addClass ( 'message' );
							//$ ( external ).addClass ( 'content' );

							//elements.content = external;
						}

						if ( ! mode || mode === 'time' )
						{
							date = new Date ( );

							date.setTime ( this.sent );

							// Generate a list of arguments.
							parameters = [
								date.getSeconds ( ), // Seconds.
								date.getMinutes ( ), // Minutes.
								date.getHours ( ), // Hours (24).
								date.getHours ( ) < 11 ? date.getHours ( ) === 0 ? 12 : date.getHours ( ) : date.getHours ( ) - 12, // Hours (12).
								date.getHours ( ) < 11 ? 'am' : 'pm',
								date.getHours ( ) < 11 ? 'AM' : 'PM',
								this.language.date.weekday [ date.getDay ( ) ], // Weekday.
								date.getDate ( ), // Date.
								this.language.date.month [ date.getMonth ( ) ], // Month.
								date.getMonth ( ), // Month (Number).
								date.getFullYear ( ) // Year.
							];

							difference = ( this.time ( ) - this.sent );

							for ( i = 0; i < this.language.format.date.length; ++i )
							{
								if ( this.language.format.date [ i ].time >= difference || this.language.format.date [ i ].time === undefined )
								{
									input = this.format ( this.language.format.date [ i ].format, parameters );

									break;
								}
							}

							if ( this.language.format.time [ this.type ] !== undefined )
							{
								format = this.language.format.time [ this.type ];
							}
							else
							{
								format = this.language.format.time [ 0 ];
							}

							time = document.createElement ( 'span' );
							$ ( time ).addClass ( this.instance.name );
							$ ( time ).addClass ( 'message' );
							$ ( time ).addClass ( 'time' );
							$ ( time ).append ( this.format ( format, input ) );

							if ( mode )
							{
								return time;
							}

							//external = $ ( document.createElement ( 'span' ) ).append ( time );
							//$ ( external ).addClass ( this.instance.name );
							//$ ( external ).addClass ( 'message' );
							//$ ( external ).addClass ( 'time' );

							//elements.time = external;
						}

						if ( ! mode || mode === 'user' )
						{
							user = document.createElement ( 'span' );
							$ ( user ).addClass ( this.instance.name );
							$ ( user ).addClass ( 'message' );
							$ ( user ).addClass ( 'user' );

							if ( this.user )
							{
								$ ( user ).addClass ( 'u' + this.user.parent.server );

								if ( this.user.parent.display )
								{
									if ( this.language.format.user.standard [ this.type ] !== undefined )
									{
										format = this.language.format.user.standard [ this.type ];
									}
									else
									{
										format = this.language.format.user.standard [ 0 ];
									}

									$ ( user ).append ( this.format ( format, this.user.parent.display, this.user.parent.server ) );
								}
								else
								{
									if ( this.language.format.user.unknown [ this.type ] !== undefined )
									{
										format = this.language.format.user.unknown [ this.type ];
									}
									else
									{
										format = this.language.format.user.unknown [ 0 ];
									}

									$ ( user ).append ( this.format ( format, this.language.messages.unknown, this.user.parent.server ) );
								}

								if ( this.user.parent.colors.current !== undefined )
								{
									$ ( user ).css ( 'color', this.user.parent.colors.current );
								}
							}
							else
							{
								if ( this.language.format.user.system [ this.type ] !== undefined )
								{
									format = this.language.format.user.system [ this.type ];
								}
								else
								{
									format = this.language.format.user.system [ 0 ];
								}

								$ ( user ).append ( this.format ( format, this.language.messages.system ) );
							}

							if ( mode )
							{
								return user;
							}

							//external = $ ( document.createElement ( 'span' ) ).append ( user );
							//$ ( external ).addClass ( this.instance.name );
							//$ ( external ).addClass ( 'message' );
							//$ ( external ).addClass ( 'user' );

							//elements.user = external;
						}

						// Assume that no invalid calls will ever be made.

						//clear = document.createElement ( 'div' );
						//$ ( clear ).addClass ( this.instance.name );
						//$ ( clear ).addClass ( 'message' );
						//$ ( clear ).addClass ( 'clear' );

						//internal = document.createElement ( 'div' );
						//$ ( internal ).addClass ( this.instance.name );
						//$ ( internal ).addClass ( 'message' );
						//$ ( internal ).addClass ( 'internal' );
						//$ ( internal ).append ( time );
						//$ ( internal ).append ( user );
						//$ ( internal ).append ( content );
						//$ ( internal ).append ( clear );

						container = document.createElement ( 'p' );
						$ ( container ).addClass ( this.instance.name );
						$ ( container ).addClass ( 'message' );
						$ ( container ).addClass ( 'container' );
						$ ( container ).addClass ( 'p' + this.power );
						$ ( container ).addClass ( 't' + this.type );
						//$ ( container ).append ( internal );
						$ ( container ).append ( time );
						$ ( container ).append ( user );
						$ ( container ).append ( content );
						//$ ( container ).append ( clear );

						if ( this.level !== -1 )
						{
							$ ( container ).addClass ( 'l' + this.level + '-' + this.line );
						}

						return {
							container: container,
							content: content,
							replace: undefined,
							time: time,
							user: user
						};
					},

					/*
					 * Hide message function
					 *
					 * @return null
					 */
					hide: function ( )
					{
						if ( ! this.visible )
						{
							return null;
						}

						this.visible = false;

						if ( this.instance.scrolling.visible ( this, 0 ) && this.instance.animations )
						{
							$ ( this.elements.container ).hide ( 250 );
						}
						else
						{
							$ ( this.elements.container ).hide ( );
						}

						return null;
					},

					/*
					 * Constructor function
					 *
					 * @return null
					 */
					initiate: function ( instance, message, injected )
					{
						// Declare variables.
						var animations, sibling;

						if ( ! message.content || typeof ( message.content ) !== 'string' )
						{
							return null;
						}

						// Define reference variables.
						/*
						 * We can skip this as it's defined near the end.
						this.elements = {
							container: undefined,
							message: undefined,
							user: undefined
						};
						*/
						this.identifiers = {
							type: -1,
							user: -1
						};

						// Create a reference to our parent.
						this.instance = instance;
						this.local = this.instance.messages;

						this.identifier = this.local.storage.length;

						if ( ! message.raw )
						{
							this.content = message.content;
						}
						else
						{
							this.content = this.instance.parse ( message.content );
						}

						if ( typeof ( message.level ) === 'number' && typeof ( message.line ) === 'number' )
						{
							this.level = message.level;
							this.line = message.line;

							if ( ! this.local.levels [ this.level ] )
							{
								this.local.levels [ this.level ] = [ ];
							}

							this.local.levels [ this.level ] [ this.line ] = this;

							sibling = this.local.sibling ( this.level, this.line );
						}

						if ( typeof ( message.power ) === 'number' )
						{
							this.power = message.power;
						}

						if ( typeof ( message.type ) === 'number' )
						{
							this.type = message.type;
						}

						if ( ! this.local.types [ this.type ] )
						{
							this.local.types [ this.type ] = [ ];
						}

						this.identifiers.type = this.local.types [ this.type ].length;
						this.local.types [ this.type ].push ( this );

						if ( typeof ( message.time ) === 'number' )
						{
							this.sent = message.time;
						}
						else
						{
							this.sent = this.time ( );
						}

						if ( typeof ( message.user ) === 'number' )
						{
							this.user = this.instance.users.get ( message.user );

							this.identifiers.user = this.user.messages.length;
							this.user.messages.push ( this );

							this.user.update (
							{
								status: true,
								time: this.sent
							} );
						}

						this.log ( 'created message #', this.identifier, ' in instance #', this.instance.identifier.data );

						this.local.storage.push ( this );

						// Generate element.
						this.elements = this.generate ( );

						animations = this.instance.scrolling.visible ( sibling, 1 ) && this.instance.animations;

						if ( animations || ! this.visible )
						{
							$ ( this.elements.container ).hide ( );
						}

						this.append ( sibling );

						if ( this.visible && animations )
						{
							$ ( this.elements.container ).show ( 250, this.instance.closure ( this.instance.append, [ injected ] ) );
						}
						else
						{
							this.instance.append ( injected );
						}

						return null;
					},

					/*
					 * Replace message function
					 *
					 * Animated replace. We assume this was not called when the element was out of view.
					 *
					 * @return null
					 */
					replace: function ( mode, call )
					{
						if ( ! this.instance.animations )
						{
							return null;
						}

						// Assume no input is invalid.

						if ( ! call )
						{
							$ ( this.elements [ mode ] ).hide ( 250, this.closure ( this.replace, [ mode, true ] ) );
						}
						else
						{
							if ( this.instance.animations || ( mode === 'container' && ! this.visible ) )
							{
								$ ( this.elements.replace [ mode ] ).hide ( );
							}

							if ( mode === 'container' )
							{
								if ( ! this.move )
								{
									$ ( this.elements [ mode ] ).replaceWith ( this.elements.replace [ mode ] );

									this.elements = this.elements.replace;
									this.elements.replace = undefined;
								}
								else
								{
									$ ( this.elements [ mode ] ).remove ( );
									this.elements = this.elements.replace;

									this.append ( this.move );
									this.move = undefined;
								}

								if ( this.visible )
								{
									$ ( this.elements [ mode ] ).show ( 250 );
								}
							}
							else
							{
								$ ( this.elements [ mode ] ).replaceWith ( this.elements.replace [ mode ] );

								if ( ! this.elements.replace.container )
								{
									this.elements [ mode ] = this.elements.replace [ mode ];
									this.elements.replace [ mode ] = undefined;
								}
								else
								{
									this.elements [ mode ] = $ ( this.elements.replace [ mode ] ).clone ( );
								}

								$ ( this.elements [ mode ] ).show ( 250 );
							}
						}

						return null;
					},

					/*
					 * Show message function
					 *
					 * @return null
					 */
					show: function ( )
					{
						if ( this.visible )
						{
							return null;
						}

						this.visible = true;

						if ( this.instance.scrolling.visible ( this, 0 ) && this.instance.animations )
						{
							$ ( this.elements.container ).show ( 250 );
						}
						else
						{
							$ ( this.elements.container ).show ( );
						}

						return null;
					},

					/*
					 * Update message function
					 *
					 * @return null
					 */
					update: function ( message )
					{
						// Declare variables.
						var animations, content, elements, i, move, replace, type, time, user;

						if ( typeof ( message.transaction ) === 'number' )
						{
							if ( this.instance.handler.messages.current [ message.transaction ] && this.identifier === this.instance.handler.messages.current [ message.transaction ].identifier )
							{
								if ( message.transaction === this.instance.handler.messages.current.length - 1 )
								{
									do
									{
										this.instance.handler.messages.current.pop ( );
									}
									while ( this.instance.handler.messages.current [ this.instance.handler.messages.current.length - 1 ] === null );
								}
								else
								{
									this.instance.handler.messages.current [ message.transaction ] = null;
									this.instance.handler.messages.free.push ( message.transaction );
									this.instance.handler.messages.free.sort ( );
								}
							}
						}

						if ( this.level === -1 && typeof ( message.level ) === 'number' && typeof ( message.line ) === 'number' )
						{
							this.level = message.level;
							this.line = message.line;

							if ( ! this.local.levels [ this.level ] )
							{
								this.local.levels [ this.level ] = [ ];
							}

							this.local.levels [ this.level ] [ this.line ] = this;

							this.move = this.local.sibling ( this.level, this.line );

							move = true;
						}

						if ( typeof ( message.type ) !== 'number' )
						{
							message.type = 0;
						}

						if ( message.type !== this.type )
						{
							this.local.types [ this.type ].splice ( this.identifiers.type, 1 );

							for ( i = this.identifiers.type; i < this.local.types [ this.type ].length; ++i )
							{
								--this.local.types [ this.type ] [ i ].identifiers.type;
							}

							if ( ! this.local.types [ message.type ] )
							{
								this.local.types [ message.type ] = [ ];
							}

							this.identifiers.type = this.local.types [ message.type ].length;
							this.local.types [ message.type ].push ( this );

							type = true;
						}

						if ( message.content != this.content && message.content && typeof ( message.content ) === 'string' )
						{
							if ( ! message.raw )
							{
								this.content = message.content;

								content = true;
							}
							else
							{
								message.content = this.instance.parse ( message.content );

								if ( message.content != this.content )
								{
									this.content = message.content;

									content = true;
								}
							}
						}
						else if ( type && ( ( this.language.format.content [ message.type ] !== undefined ? this.language.format.content [ message.type ] : this.language.format.content [ 0 ] ) !== ( this.language.format.content [ this.type ] !== undefined ? this.language.format.content [ this.type ] : this.language.format.content [ 0 ] ) ) )
						{
							content = true;
						}

						if ( message.time !== this.sent && typeof ( message.time ) === 'number' )
						{
							this.sent = message.time;

							time = true;
						}
						else if ( type && ( ( this.language.format.time [ message.type ] !== undefined ? this.language.format.time [ message.type ] : this.language.format.time [ 0 ] ) !== ( this.language.format.time [ this.type ] !== undefined ? this.language.format.time [ this.type ] : this.language.format.time [ 0 ] ) ) )
						{
							time = true;
						}

						if ( ( ! this.user && typeof ( message.user ) === 'number' ) || ( this.user && message.user !== this.user.parent.server ) )
						{
							if ( this.identifiers.user !== -1 )
							{
								this.user.messages.splice ( this.identifiers.user, 1 );

								for ( i = this.identifiers.user; i < this.user.messages.length; ++i )
								{
									--this.user.messages [ i ].identifiers.user;
								}

								this.user = undefined;
							}

							if ( typeof ( message.user ) === 'number' )
							{
								this.user = this.instance.users.get ( message.user );

								this.identifiers.user = this.user.messages.length;
								this.user.messages.push ( this );
							}
							else
							{
								this.user = undefined;

								this.identifiers.user = -1;
							}

							user = true;
						}
						// Yeah... I should have found a better way to format the following line.
						else if ( type && ( ( this.user && ( this.user.parent.display && ( ( this.language.format.user.standard [ message.type ] !== undefined ? this.language.format.user.standard [ message.type ] : this.language.format.user.standard [ 0 ] ) !== ( this.language.format.user.standard [ this.type ] !== undefined ? this.language.format.user.standard [ this.type ] : this.language.format.user.standard [ 0 ] ) ) ) || ( ( this.language.format.user.unknown [ message.type ] !== undefined ? this.language.format.user.unknown [ message.type ] : this.language.format.user.unknown [ 0 ] ) !== ( this.language.format.user.unknown [ this.type ] !== undefined ? this.language.format.user.unknown [ this.type ] : this.language.format.user.unknown [ 0 ] ) ) ) || ( ( this.language.format.user.system [ message.type ] !== undefined ? this.language.format.user.system [ message.type ] : this.language.format.user.system [ 0 ] ) !== ( this.language.format.user.system [ this.type ] !== undefined ? this.language.format.user.system [ this.type ] : this.language.format.user.system [ 0 ] ) ) ) )
						{
							user = true;
						}

						if ( this.user )
						{
							this.user.update (
							{
								status: true,
								time: this.sent
							} );
						}

						this.type = message.type;

						animations = this.instance.scrolling.visible ( this, 0 ) && this.instance.animations;

						// Update entire message?
						if ( content && time && user )
						{
							elements = this.generate ( );

							if ( animations )
							{
								replace = ! this.elements.replace || ! this.elements.replace.container;

								this.elements.replace = elements;

								if ( replace )
								{
									// Que replace.
									this.replace ( 'container' );
								}
							}
							else
							{
								if ( ! move )
								{
									$ ( this.elements.container ).replaceWith ( elements.container );
									this.elements = elements;
								}
								else
								{
									$ ( this.elements.container ).remove ( );
									this.elements = elements;

									this.append ( this.move );
									this.move = undefined;
								}
							}

							return null;
						}

						if ( ! this.elements.replace )
						{
							this.elements.replace = {
								container: undefined,
								content: undefined,
								time: undefined,
								user: undefined
							};
						}

						if ( content )
						{
							content = this.generate ( 'content' );

							if ( animations )
							{
								if ( ! move && ! this.elements.replace.content )
								{
									this.elements.replace.content = content;
									this.replace ( 'content' );
								}
								else if ( this.elements.replace.container )
								{
									$ ( this.elements.replace.content ).replaceWith ( content );
									this.elements.replace.content = content;
								}
								else
								{
									this.elements.replace.content = content;
								}
							}
							else
							{
								$ ( this.elements.content ).replaceWith ( content );
								this.elements.content = content;
							}
						}

						if ( time )
						{
							time = this.generate ( 'time' );

							if ( animations )
							{
								if ( ! move && ! this.elements.replace.time )
								{
									this.elements.replace.time = time;
									this.replace ( 'time' );
								}
								else if ( this.elements.replace.container )
								{
									$ ( this.elements.replace.time ).replaceWith ( time );
									this.elements.replace.time = time;
								}
								else
								{
									this.elements.replace.time = time;
								}
							}
							else
							{
								$ ( this.elements.time ).replaceWith ( time );
								this.elements.time = time;
							}
						}

						if ( user )
						{
							user = this.generate ( 'user' );

							if ( animations )
							{
								if ( ! move && ! this.elements.replace.user )
								{
									this.elements.replace.user = user;
									this.replace ( 'user' );
								}
								else if ( this.elements.replace.container )
								{
									$ ( this.elements.replace.user ).replaceWith ( user );
									this.elements.replace.user = user;
								}
								else
								{
									this.elements.replace.user = user;
								}
							}
							else
							{
								$ ( this.elements.user ).replaceWith ( user );
								this.elements.user = user;
							}
						}

						if ( move )
						{
							if ( ! this.elements.replace.container )
							{
								if ( ! this.elements.replace.content )
								{
									this.elements.replace.content = $ ( this.elements.content ).clone ( );
								}

								if ( ! this.elements.replace.time )
								{
									this.elements.replace.time = $ ( this.elements.time ).clone ( );
								}

								if ( ! this.elements.replace.user )
								{
									this.elements.replace.user = $ ( this.elements.user ).clone ( );
								}

								this.elements.replace = this.generate ( true, this.elements.replace.content, this.elements.replace.time, this.elements.replace.user );
							}

							if ( animations )
							{
								this.replace ( 'container' );
							}
							else
							{
								$ ( this.elements.container ).remove ( );
								this.elements = this.elements.replace;

								this.append ( this.move );
								this.move = undefined;
							}
						}

						return null;
					}
				} ),

				/*
				 * Close messages function
				 *
				 * @return null
				 */
				close: function ( )
				{
					// Declare variables.
					var i;

					for ( i = this.storage.length - 1; i >= 0; --i )
					{
						this.storage [ i ].close ( );
					}

					// Clear references.
					this.instance = undefined;

					return null;
				},

				/*
				 * Constructor function
				 *
				 * @return null
				 */
				initiate: function ( instance )
				{
					// Define reference variables.
					this.append = [ ];
					this.levels = [ ];
					this.storage = [ ];
					this.types = [ ];

					this.instance = instance;

					return null;
				},

				/*
				 * Make message function
				 *
				 * @return null
				 */
				make: function ( message, injected )
				{
					// Declare variables.
					var existing;

					if ( typeof ( message.level ) === 'number' && typeof ( message.line ) === 'number' )
					{
						if ( this.levels [ message.level ] )
						{
							existing = this.levels [ message.level ] [ message.line ];

							if ( existing )
							{
								existing.update ( message );
								this.instance.append ( injected );

								return existing;
							}
						}
					}

					if ( typeof ( message.transaction ) === 'number' )
					{
						existing = this.instance.handler.messages.current [ message.transaction ];

						if ( existing )
						{
							existing.update ( message );
							this.instance.append ( injected );

							return existing;
						}
					}

					// Create new message.
					return new this.message ( this.instance, message, injected );
				},

				/*
				 * Get sibling function
				 *
				 * @return object|undefined
				 */
				sibling: function ( level, line )
				{
					// Declare variables.
					var i, j, sibling;

					if ( level > this.instance.level || ( level === this.instance.level && line > this.instance.line ) )
					{
						// Update local count.
						this.instance.level = level;
						this.instance.line = line;
					}
					else
					{
						// Find the nearest sibling message.
						for ( i = level; i < this.levels.length; ++i )
						{
							if ( ! this.levels [ i ] )
							{
								continue;
							}

							for ( j = ( i !== level ? 0 : line + 1 ); j < this.levels [ i ].length; ++j )
							{
								if ( ! this.levels [ i ] [ j ] )
								{
									continue;
								}

								sibling = this.levels [ i ] [ j ];

								break;
							}

							if ( sibling )
							{
								break;
							}
						}

						if ( ! sibling )
						{
							this.instance.level = level;
							this.instance.line = line;
						}
					}

					return sibling;
				}
			} ),

			/*
			 * Local users class
			 * @package Blink
			 *
			 * Singleton
			 */
			scrolling: object.make ( null, true, {
				instance: undefined,
				storage: undefined, // Defined in initiate.

				log: method.get ( 'log' ),

				/*
				 * Get bottom function
				 *
				 * @return integer
				 */
				bottom: function ( )
				{
					return parseInt ( $ ( this.instance.elements.wrap ).attr( 'scrollHeight' ) ) - $ ( this.instance.elements.wrap ).height ( ) - parseInt ( $ ( this.instance.elements.wrap ).css ( 'paddingTop' ) ) - ( parseInt ( $ ( this.instance.elements.wrap ).css ( 'paddingBottom' ) ) * 2 ) - ( parseInt ( $ ( this.instance.elements.container ).css ( 'paddingBottom' ) ) );
				},

				/*
				 * Store position function
				 *
				 * @return null
				 */
				get: function ( )
				{
					this.storage.bottom = this.bottom ( );
					this.storage.top = this.top ( );

					return null;
				},

				/*
				 * Animated scroll to position function
				 *
				 * @return null
				 */
				go: function ( position )
				{
					return null;
				},

				/*
				 * Constructor function
				 *
				 * @return null
				 */
				initiate: function ( instance )
				{
					this.instance = instance;

					// Define reference variables.
					this.storage = {
						current: -1,
						bottom: -1,
						set: false,
						top: -1
					};

					return null;
				},

				/*
				 * Restore position function
				 *
				 * @return null
				 */
				set: function ( )
				{
					//this.top ( this.storage.top + ( this.bottom ( ) - this.storage.bottom ) );

					return null;
				},

				/*
				 * Get/Jump to position function
				 *
				 * @return null
				 */
				top: function ( position )
				{
					return $ ( this.instance.elements.wrap ).scrollTop ( position );
				},

				/*
				 * Is item visible function
				 *
				 * @return null
				 */
				visible: function ( message )
				{
					var bottom, top;

					top = this.top ( );

					if ( message )
					{
						bottom = message.elements.container.offsetTop - this.instance.elements.wrap.offsetTop;

						if ( ( top < bottom && top + $ ( this.instance.elements.wrap ).height ( ) > bottom ) || $ ( this.instance.elements.wrap ).height ( ) > parseInt ( $ ( this.instance.elements.wrap ).attr( 'scrollHeight' ) ) )
						{
							return true;
						}

						return false;
					}

					bottom = this.bottom ( );

					if ( top > bottom || $ ( this.instance.elements.wrap ).height ( ) > parseInt ( $ ( this.instance.elements.wrap ).attr( 'scrollHeight' ) ) )
					{
						return true;
					}

					return false;
				}
			} ),

			/*
			 * Local users class
			 * @package Blink
			 *
			 * Singleton
			 */
			users: object.make ( null, true, {
				handler: undefined,
				identifiers: undefined, // Defined in initiate.
				storage: undefined, // Defined in initiate.
				users: undefined, // Defined in initiate.

				/*
				 * Local user class
				 * @package Blink
				 *
				 * Factory
				 */
				user: object.make ( null, true, {
					activity: 0,
					display: '',
					element: undefined,
					identifier: -1,
					identifiers: undefined, // Defined in initiate.
					instance: undefined,
					local: undefined,
					messages: undefined, // Defined in initiate.
					parent: undefined,
					status: false,
					timers: undefined, // Defined in initiate.
					visible: false,

					blink: object.get ( 'internal', false ),
					closure: method.get ( 'closure' ),
					log: method.get ( 'log' ),
					time: method.get ( 'time' ),
					timer: object.get ( 'timer', true ),

					/*
					 * Append function
					 *
					 * @return null
					 */
					append: function ( element )
					{
						// Declare variables.
						var current, elements, index;

						index = $.inArray ( this.display, this.local.users );

						if ( index !== -1 )
						{
							this.local.users.splice ( index, 1 );
						}

						this.local.users.push ( this.parent.list );
						this.local.users.sort ( ); // @todo: Implement a better sorting mechanism.

						current = $.inArray ( this.parent.list, this.local.users );

						// Assume no error occured.

						// @todo: implement scrolling.visible function for user list.
						if ( this.instance.animations || ( ! this.visible && this.parent.server !== this.instance.handler.user ) )
						{
							$ ( element ).hide ( );
						}

						if ( current !== index )
						{
							if ( this.element )
							{
								$ ( this.element ).remove ( );
							}

							if ( current === this.local.users.length - 1 )
							{
								$ ( this.instance.elements.users ).append ( element );
							}
							else
							{
								elements = $ ( '.user', this.instance.elements.users );
								$ ( elements [ current ] ).before ( element );
							}
						}
						else
						{
							$ ( this.element ).replaceWith ( element );
						}

						// @todo: implement scrolling.visible function for user list.
						if ( ( this.visible || this.parent.server === this.instance.handler.user ) && this.instance.animations )
						{
							$ ( element ).show ( 250 );
						}

						this.display = this.parent.list;
						this.element = element;

						return null;
					},

					/*
					 * User status function
					 *
					 * @return null
					 */
					check: function ( )
					{
						// Declare variables.
						var duration;

						if ( ! this.visible )
						{
							return null;
						}

						duration = this.blink.offline - ( this.time ( ) - this.activity );

						if ( duration < 1 )
						{
							this.hide ( );

							return null;
						}

						// Reset the timeout.
						this.timers.offline.reset ( duration );

						return null;
					},

					/*
					 * Close user function
					 *
					 * @return null
					 */
					close: function ( )
					{
						// Declare variables.
						var i, index;

						if ( this.timers.offline )
						{
							this.timers.offline.destroy ( );
							this.timers.offline = undefined;
						}

						this.local.storage.splice ( this.identifier, 1 );

						for ( i = this.identifier; i < this.local.storage.length; ++i )
						{
							--this.local.storage [ i ].identifier;
						}

						this.parent.local.splice ( this.identifiers.parent, 1 );

						for ( i = this.identifiers.parent; i < this.parent.local.length; ++i )
						{
							--this.parent.local [ i ].identifiers.parent;
						}

						index = $.inArray ( this.display, this.local.users );

						if ( index !== -1 )
						{
							this.local.users.splice ( index, 1 );
						}

						this.log ( 'destroyed local user #', this.identifier, ' in instance #', this.instance.identifier.data );

						// Clear references.
						this.instance = undefined;
						this.local = undefined;
						this.parent = undefined;

						this.identifier = -1;
						this.identifiers.parent = -1;

						/*
						 * We assume the following at this point:
						 *	All messages referencing to this object are, themselves, closed.
						 */

						return null;
					},

					/*
					 * Hide user function
					 *
					 * @return null
					 */
					hide: function ( )
					{
						if ( ! this.visible || this.parent.server === this.instance.handler.user )
						{
							return null;
						}

						this.visible = false;

						// @todo: implement scrolling.visible function for user list.
						if ( this.instance.animations )
						{
							$ ( this.element ).hide ( 250 );
						}
						else
						{
							$ ( this.element ).hide ( );
						}

						if ( this.timers.offline )
						{
							// Clear the timeout.
							this.timers.offline.clear ( );
						}

						return null;
					},

					/*
					 * Constructor function
					 *
					 * @return null
					 */
					initiate: function ( instance, user )
					{
						// Create a reference to our parent.
						this.instance = instance;
						this.local = this.instance.users;
						this.parent = this.instance.handler.users.get ( user );

						// Define reference variables.
						this.identifiers = {
							index: undefined,
							parent: this.parent.local.length
						};
						this.messages = [ ];

						// Define timers.
						this.timers = {
							offline: new this.timer ( 0, true )
						};

						this.timers.offline.bind ( 'run', this.closure ( this.check, [ ] ) );

						this.identifier = this.local.storage.length;

						this.log ( 'created local user #', this.identifier, ' in instance #', this.instance.identifier.data );

						this.local.identifiers.push ( user );
						this.local.storage.push ( this );
						this.parent.local.push ( this );

						this.replace ( );

						return null;
					},

					/*
					 * Show user function
					 *
					 * @return null
					 */
					show: function ( )
					{
						// Declare variables.
						var duration;

						if ( this.visible || ! this.status || this.parent.server === this.instance.handler.user )
						{
							return null;
						}

						duration = this.blink.offline - ( this.time ( ) - this.activity );

						if ( duration < 1 )
						{
							return null;
						}

						this.visible = true;

						// @todo: implement scrolling.visible function for user list.
						if ( this.instance.animations )
						{
							$ ( this.element ).show ( 250 );
						}
						else
						{
							$ ( this.element ).show ( );
						}

						// Reset the timeout.
						this.timers.offline.reset ( duration );

						return null;
					},

					/*
					 * Replace function
					 *
					 * @return null
					 */
					replace: function ( call )
					{
						// Declare variables.
						var user;

						// @todo: implement scrolling.visible function for user list.
						if ( ! call && this.element && this.instance.animations )
						{
							$ ( this.element ).hide ( 250, this.closure ( this.replace, [ true ] ) );

							return null;
						}

						user = this.parent.element.cloneNode ( true );
						$ ( user ).addClass ( this.instance.name );
						$ ( user ).addClass ( 's' + this.parent.status );

						this.append ( user );

						return null;
					},

					/*
					 * Update user function
					 *
					 * @return null
					 */
					update: function ( data )
					{
						if ( data.time > this.activity && typeof ( data.time ) === 'number' )
						{
							this.activity = data.time;

							if ( this.time ( ) - this.activity >= this.blink.offline || ! data.status )
							{
								this.status = false;

								this.hide ( );
							}
							else if ( data.status )
							{
								this.status = true;

								this.show ( );
							}
						}

						return null;
					}
				} ),

				/*
				 * Close users function
				 *
				 * @return null
				 */
				close: function ( )
				{
					// Declare variables.
					var i;

					for ( i = this.storage.length - 1; i >= 0; --i )
					{
						this.storage [ i ].close ( );
					}

					// Clear references.
					this.instance = undefined;

					return null;
				},

				/*
				 * Get user function
				 *
				 * @return null
				 */
				get: function ( user )
				{
					// Declare variables.
					var index;

					index = $.inArray ( user, this.identifiers );

					if ( index === -1 )
					{
						// Create new user.
						return ( new this.user ( this.instance, user ) );
					}

					return this.storage [ index ];
				},

				/*
				 * Constructor function
				 *
				 * @return null
				 */
				initiate: function ( instance )
				{
					// Define reference variables.
					this.identifiers = [ ];
					this.storage = [ ];
					this.users = [ ];

					this.instance = instance;

					return null;
				}
			} ),

			/*
			 * Run action function
			 *
			 * @return boolean
			 */
			action: function ( action )
			{
				switch ( action )
				{
					case 'close':
						this.close ( );
					break;
				}

				return true;
			},

			/*
			 * Append message function
			 *
			 * @return null
			 */
			append: function ( injected )
			{
				// Declare variables.
				var bottom, top;

				if ( ! this.messages.append.length )
				{
					this.messages.active = false;

					// @todo: More scrolling functionality.

					bottom = this.scrolling.bottom ( );
					top = this.scrolling.top ( );

					if ( this.scrolling.storage.current !== bottom && this.scrolling.storage.current - top < this.blink.scrolling && top < bottom )
					{
						if ( this.animations )
						{
							$ ( this.elements.wrap ).animate (
							{
								scrollTop: bottom
							},
							{
								duration: 750
							} );
						}
						else
						{
							$ ( this.elements.wrap ).scrollTop ( bottom );
						}
					}

					this.scrolling.storage.set = false;

					return null;
				}

				if ( injected )
				{
					return null;
				}

				if ( ! this.scrolling.storage.set )
				{
					this.scrolling.storage.set = true;

					this.scrolling.storage.current = bottom ? bottom : this.scrolling.bottom ( );
				}

				this.messages.active = true;

				this.messages.make ( this.messages.append.shift ( ) );

				return null;
			},

			/*
			 * Change data function
			 *
			 * @return boolean
			 */
			change: function ( data )
			{
				this.log ( 'instance #', this.identifier.original, ' changed to instance #', this.identifier.data );

				this.identifier.original = this.identifier.data;

				return null;
			},

			/*
			 * Destructor function
			 *
			 * @return null
			 */
			close: function ( )
			{
				// Declare variables.
				var i;

				this.messages.close ( );
				this.users.close ( );

				// Remove the reference to self.
				this.handler.channels.splice ( this.identifier.data, 1 );
				this.handler.instances.splice ( this.identifier.data, 1 );

				for ( i = this.identifier.data; i < this.handler.instances.length; ++i )
				{
					this.handler.instances [ i ].identifier.decrease ( );
				}

				this.log ( 'destroyed instance #', this.identifier.data, ' in handler #', this.handler.identifier.data );

				this.identifier.unbind ( );
				this.identifier.change ( -1 );

				// Clear references.
				this.handler = undefined;

				this.elements = {
					container: undefined,
					form: undefined,
					users: undefined,
					wrap: undefined
				};

				return null;
			},

			/*
			 * Constructor function
			 *
			 * @return null
			 */
			initiate: function ( handler, channel, element )
			{
				// Declare variables.
				var elements;

				// Define reference variables.
				this.build = {
					action: 0,
					level: 0,
					line: 0,
					messages: [ ]
				};
				this.elements = {
					container: undefined,
					form: undefined,
					users: undefined,
					wrap: undefined
				};
				this.messages = new this.messages ( this );
				this.scrolling = new this.scrolling ( this );
				this.users = new this.users ( this );

				// Create a reference to our handler.
				this.handler = handler;
				this.channel = channel;

				this.identifier = new this.data ( this.handler.instances.length );
				this.identifier.bind ( 'change', this.closure ( this.change, [ ] ) );
				this.handler.instances.push ( this );
				this.handler.channels.push ( this.channel );

				this.log ( 'created instance #', this.identifier.data, ' for channel ', this.channel, ' in handler #', this.handler.identifier.data );

				elements = {
					container: undefined,
					form: $ ( '.form', element ),
					users: $ ( '.users.container', element ),
					wrap: $ ( '.history.wrap', element )
				};

				if ( elements.form.length )
				{
					this.elements.form = elements.form [ 0 ];

					$ ( this.elements.form ).submit ( this.closure ( this.submit, [ ] ) );

					elements.message = $ ( '.input.message', this.elements.form );

					if ( elements.message.length )
					{
						this.elements.message = this.elements.message;
					}
					else
					{
						elements.message = $ ( '.textarea.message', this.elements.form );

						if ( elements.message.length )
						{
							this.input = false;
							this.elements.message = elements.message;

							$ ( this.elements.message ).keypress ( this.closure ( this.scanner ) );
						}
					}
				}

				if ( elements.wrap.length )
				{
					elements.container = $ ( '.history.container', elements.wrap );

					if ( elements.container.length )
					{
						this.elements.container = elements.container [ 0 ];
						this.elements.wrap = elements.wrap [ 0 ];

						// Reset scrollTop to 0. Firefox likes to scroll down with appended elements if the user refreshes.
						$ ( this.elements.wrap ).scrollTop ( 0 );
					}
				}

				if ( elements.users.length )
				{
					this.elements.users = elements.users [ 0 ];
				}

				// Is there a local user?
				if ( this.handler.user > -1 )
				{
					this.users.get ( this.handler.user );
				}

				return null;
			},

			/*
			 * Parse message function
			 *
			 * Creates an HTML representation of BBCode. Strips existing HTML.
			 *
			 * @return string
			 */
			parse: function ( input )
			{
				// Declare variables.
				var string;

				string = input.replace ( /&/g, '&amp;' );
				string = string.replace ( /\</g, '&lt;' );
				string = string.replace ( /\>/g, '&gt;' );

				string = parseBBCode ( string );

				return string;
			},

			/*
			 * Position cursor function
			 *
			 * Moves the cursor to set position within the message box.
			 *
			 * @return null
			 */
			position: function ( place )
			{
				// Declare variables.
				var range;

				if ( this.elements.message.setSelectionRange )
				{
					this.elements.message.setSelectionRange ( place, place );
				}
				else if ( this.elements.message.createTextRange )
				{
					range = this.elements.message.createTextRange ( );
					range.collapse ( true );
					range.moveEnd ( 'character', place );
					range.moveStart ( 'character', place );
					range.select ( );
				}

				return null;
			},

			/*
			 * Response function
			 *
			 * @return null
			 */
			response: function ( document )
			{
				// Declare variables.
				var i, user;

				if ( document )
				{
					if ( document.users )
					{
						this.log ( 'received ', document.users.length, ' users in instance #', this.identifier.data );

						for ( i = 0; i < document.users.length; ++i )
						{
							user = this.users.get ( document.users [ i ].identifier );

							// Manipulate users.
							user.update ( document.users [ i ] );
						}
					}

					if ( document.messages )
					{
						this.log ( 'received ', document.messages.length, ' messages in instance #', this.identifier.data );

						this.messages.append.push.apply ( this.messages.append, document.messages );

						if ( ! this.messages.active )
						{
							this.append ( );
						}
					}
				}

				return null;
			},

			/*
			 * Key scanner function
			 *
			 * @return boolean
			 */
			scanner: function ( input )
			{
				// Declare variables.
				var key, message;

				if ( window.event )
				{
					key = input.keyCode;
				}
				else
				{
					key = input.which;
				}

				if ( key === 13 && ! input.shiftKey )
				{
					this.submit ( );

					return false;
				}

				return true;
			},

			/*
			 * Submit form function
			 *
			 * @return false
			 */
			submit: function ( )
			{
				// Declare variables.
				var content;

				content = $ ( this.elements.message ).val ( );

				// Preserve whitespace if there are multiple lines.
				if ( ! /(?:\n|\r)/gi.test ( content ) )
				{
					// Strip all excess whitespace.
					content = content.replace ( /(?:\s{2,}|\t)/gi, ' ' );
				}

				// Is there anything but whitespace?
				if ( ! /(?:[^\s])/gi.test ( content ) )
				{
					return false;
				}

				var message = this.messages.make (
				{
					content: this.parse ( content ),
					type: 1,
					user: this.handler.user > -1 ? this.handler.user : undefined
				}, true );

				$ ( this.elements.message ).val ( '' );

				this.build.messages.push ( message );
				this.handler.cache = false;

				if ( this.scrolling.top ( ) < this.scrolling.bottom ( ) )
				{
					// Scroll to bottom.
					$ ( this.elements.wrap ).animate (
					{
						scrollTop: this.scrolling.bottom ( )
					},
					{
						duration: 750
					} );
				}

				// Are we using comet?
				if ( this.handler.comet )
				{
					this.handler.reset ( );
				}

				return false;
			}
		} ),

		/*
		 * Language class
		 * @package Blink
		 *
		 * Singleton
		 */
		language: object.make ( 'language', false, {
			date: {
				month: [
					'January',
					'February',
					'March',
					'April',
					'May',
					'June',
					'July',
					'August',
					'September',
					'October',
					'November',
					'December'
				],
				weekday: [
					'Sunday',
					'Monday',
					'Tuesday',
					'Wednesday',
					'Thursday',
					'Friday',
					'Saturday'
				]
			},
			format: { // Use "\u00a0" if non-breaking spaces are needed.
				content: [
					'%1$s'
				],
				date: [
					{
						format: 'Future', // The Future
						time: -1
					},
					{
						format: '%3$02d:%2$02d', // 16:42
						time: 86400000
					},
					{
						format: '%7$.5s', // Thurs
						time: 604800000
					},
					{
						format: '%9$.3s %8$02d', // Dec 05
						time: 31536000000
					},
					{
						format: '%11$04d', // 2010
						time: undefined
					}
				],
				time: [
					'%1$s'
				],
				user: {
					list: {
						standard: '%1$s',
						unknown: 'User #%2$d'
					},
					standard: [
						'[%1$s]:',
						'\u2192 Sending:'
					],
					system: [
						'System:',
						'\u2192 Sending:'
					],
					unknown: [
						'User #%2$d:',
						'\u2192 Sending:'
					]
				}
			},
			messages: {
				system: 'System',
				unknown: 'Unknown'
			}
		} ),

		/*
		 * Timer class
		 * @package Blink
		 *
		 * Factory
		 */
		timer: object.make ( 'timer', true, {
			// Closure.
			call: undefined,

			data: object.get ( 'data', true, false ),
			bind: method.get ( 'bind' ),
			blink: object.get ( 'internal', false, false ),
			closure: method.get ( 'closure' ),
			log: method.get ( 'log' ),
			trigger: method.get ( 'trigger' ),
			unbind: method.get ( 'unbind' ),

			// Storage variables.
			active: false,
			delay: 0,
			identifier: -1,
			permanent: false,
			resource: undefined,

			/*
			 * Clear timeout function
			 *
			 * @return boolean
			 */
			clear: function ( log )
			{
				if ( this.identifier.data === -1 )
				{
					return false;
				}

				// Trigger the clear event.
				this.trigger ( 'clear', [ this ] );

				if ( this.active )
				{
					clearTimeout ( this.resource );

					this.active = false;
				}

				if ( ! log )
				{
					this.log ( 'cleared timeout #', this.identifier.data );
				}

				return true;
			},

			/*
			 * Change data function
			 *
			 * @return boolean
			 */
			change: function ( data )
			{
				this.log ( 'changed timeout #', this.identifier.original, ' to timeout #', this.identifier.data );

				this.identifier.original = this.identifier.data;

				return null;
			},

			/*
			 * Destroy timeout function
			 *
			 * @return boolean
			 */
			destroy: function ( )
			{
				// Declare variables.
				var i;

				// Trigger the destroy event.
				this.trigger ( 'destroy', [ this ] );

				if ( this.active )
				{
					this.clear ( true );
				}

				// Remove the reference to self.
				this.blink.timers.splice ( this.identifier.data, 1 );

				for ( i = this.identifier.data; i < this.blink.timers.length; ++i )
				{
					this.blink.timers [ i ].identifier.decrease ( );
				}

				this.log ( 'destroyed timeout #', this.identifier.data );

				this.identifier.unbind ( );
				this.identifier.change ( -1 );
			},

			/*
			 * Constructor function
			 *
			 * Creates a timeout.
			 *
			 * @return null
			 */
			initiate: function ( delay, permanent )
			{
				// Create timeout data.
				this.delay = delay;
				this.permanent = permanent;

				// Create closure.
				this.call = this.closure ( function ( )
				{
					this.active = false;

					this.run ( );

					if ( ! this.active )
					{
						if ( ! this.permanent )
						{
							this.destroy ( );
						}
						else
						{
							this.clear ( );
						}
					}

					return null;
				}, null );

				if ( this.delay )
				{
					this.resource = setTimeout ( this.call, this.delay );

					this.active = true;

					// Create a reference to self for other code to use.
					this.identifier = new this.data ( this.blink.timers.length );
					this.blink.timers.push ( this );
				}
				else
				{
					this.identifier = new this.data ( -1 );
				}

				this.identifier.bind ( 'change', this.closure ( this.change, [ ] ) );

				this.log ( 'created timeout #', this.identifier.data );

				return null;
			},

			/*
			 * Reset timeout function
			 *
			 * @return true
			 */
			reset: function ( delay )
			{
				// Trigger the reset event.
				this.trigger ( 'reset', [ this ] );

				if ( this.active )
				{
					clearTimeout ( this.resource );
				}

				if ( delay )
				{
					this.delay = delay;
				}

				this.resource = setTimeout ( this.call, this.delay );

				this.active = true;

				if ( this.identifier.data === -1 )
				{
					// Create a reference to self for other code to use.
					this.identifier.change ( this.blink.timers.length );
					this.blink.timers.push ( this );

					this.log ( 'created timeout #', this.identifier.data );
				}

				this.log ( 'reset timeout #', this.identifier.data );

				return true;
			},

			/*
			 * Run callback function
			 *
			 * @return null
			 */
			run: function ( )
			{
				this.log ( 'run timeout #', this.identifier.data );

				// Trigger the run event.
				this.trigger ( 'run', [ this ] );

				return null;
			}
		} ),

		/*
		 * Window class
		 * @package Blink
		 *
		 * Factory
		 */
		window: object.make ( null, true, {
			instance: object.get ( 'instance', true, false ),

			/*
			 * Constructor function
			 *
			 * @return null
			 */
			initiate: function ( )
			{
				return null;
			}
		} ),

		/*
		 * Close Blink function
		 *
		 * @return null
		 */
		close: function ( )
		{
			// Declare variables.
			var i;

			for ( i = 0; i < this.handlers.length; ++i )
			{
				this.handlers [ i ].close ( );
			}

			this.log ( 'Blink Messenger closed' );

			return null;
		},

		/*
		 * Bind callback function
		 *
		 * @return jQuery
		 */
		bind: method.make ( 'bind', function ( type, data, fn )
		{
			this.log ( 'bound callback to "', type, '" event on object' );

			return $ ( this ).bind ( 'blink_' + type, data, fn );
		} ),

		/*
		 * Closure creation function
		 *
		 * @return object
		 */
		closure: method.make ( 'closure', function ( method, parameters )
		{
			// Declare variables.
			var object;

			object = this;

			// Create closure.
			return function ( )
			{
				if ( ! $.isArray ( parameters ) )
				{
					parameters = arguments;
				}

				return method.apply ( object, parameters );
			};
		} ),

		/*
		 * Log message function
		 *
		 * @return boolean
		 */
		log: method.make ( 'log', function ( )
		{
			// Declare variables.
			var i, message;

			if ( ! arguments.length )
			{
				return false;
			}

			message = '';

			// Generate message.
			for ( i = 0; i < arguments.length; ++i )
			{
				message += arguments [ i ];
			}

			console.log ( 'blink: ' + message );

			return true;
		} ),

		/*
		 * Format text function
		 *
		 * sprintf() for JavaScript v.0.4
		 *
		 * Copyright (c) 2007 Alexandru Marasteanu <http://alexei.417.ro/>
		 * Thanks to David Baird (unit test and patch).
		 *
		 * This program is free software; you can redistribute it and/or modify it under
		 * the terms of the GNU General Public License as published by the Free Software
		 * Foundation; either version 2 of the License, or (at your option) any later
		 * version.
		 *
		 * This program is distributed in the hope that it will be useful, but WITHOUT
		 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
		 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
		 * details.
		 *
		 * You should have received a copy of the GNU General Public License along with
		 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
		 * Place, Suite 330, Boston, MA 02111-1307 USA
		 *
		 * 01/06/2010: Converted to a standalone function by Matthew Oltendorf.
		 * 01/07/2010: Converted to act more like vsprintf by Matthew Oltendorf.
		 * 01/09/2010: Converted to act like either sprintf, or vsprintf by Matthew Oltendorf.
		 *
		 * @return string
		 */
		format: method.make ( 'format', function ( f, input )
		{
			// Declare variables.
			var i = 1, a, o = [], m, p, c, x, str_repeat, variables;
			str_repeat = function (i, m) { for (var o = []; m > 0; --m) o[m] = i; return(o.join('')); };
			if (!$.isArray(input)) {
			  variables = arguments;
			} else {
			  variables = [f];
			  variables.push.apply(variables, input);
			}
			while (f) {
			  if ((m = /^[^\x25]+/.exec(f))) o.push(m[0]);
				else if ((m = /^\x25{2}/.exec(f))) o.push('%');
				else if ((m = /^\x25(?:(\d+)\$)?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-fosuxX])/.exec(f))) {
				  if (((a = variables[m[1] || i++]) == null) || (a == undefined)) throw('Too few arguments.');
				  if (/[^s]/.test(m[7]) && (typeof(a) != 'number'))
					throw('Expecting number but found ' + typeof(a) + ': ' + a);
				  switch (m[7]) {
					case 'b': a = a.toString(2); break;
					case 'c': a = String.fromCharCode(a); break;
					case 'd': a = parseInt(a); break;
					case 'e': a = m[6] ? a.toExponential(m[6]) : a.toExponential(); break;
					case 'f': a = m[6] ? parseFloat(a).toFixed(m[6]) : parseFloat(a); break;
					case 'o': a = a.toString(8); break;
					case 's': a = ((a = String(a)) && m[6] ? a.substring(0, m[6]) : a); break;
					case 'u': a = Math.abs(a); break;
					case 'x': a = a.toString(16); break;
					case 'X': a = a.toString(16).toUpperCase(); break;
				  }
				  a = (/[def]/.test(m[7]) && m[2] && a > 0 ? '+' + a : a);
				  c = m[3] ? m[3] == '0' ? '0' : m[3].charAt(1) : ' ';
				  x = m[5] - String(a).length;
				  p = m[5] ? str_repeat(c, x) : '';
				  o.push(m[4] ? a + p : p + a);
				}
				else throw ('Huh ?!');
			  f = f.substring(m[0].length);
			}
			return o.join('');
		} ),

		/*
		 * Scroller function
		 *
		 * @return jQuery
		 */
		scroller: function ( )
		{
			// @todo: Complete scroller function.

			// notes:
			// This sets the scroll position of the history to any element within the history.
			// $ ( this.elements.wrap ).scrollTop ( element.offsetTop - this.elements.wrap.offsetTop );

			return null;
		},

		/*
		 * Time function
		 *
		 * @return integer
		 */
		time: method.make ( 'time', function ( )
		{
			return +new Date;
		} ),

		/*
		 * Trigger callback function
		 *
		 * @return jQuery
		 */
		trigger: method.make ( 'trigger', function ( type, data )
		{
			return $ ( this ).trigger ( 'blink_' + type, data );
		} ),

		/*
		 * Unbind callback function
		 *
		 * @return jQuery
		 */
		unbind: method.make ( 'unbind', function ( )
		{
			this.log ( 'unbound callbacks on object' );

			return $ ( this ).unbind ( );
		} )
	} ),

	/*
	 * External class
	 * @package Blink
	 *
	 * Singleton
	 */
	external = {
		/*
		 * Bind function
		 *
		 * @return boolean
		 */
		bind: function ( identifier, channel, selector )
		{
			// Declare variables.
			var element, handler;

			if ( this.locked || ! ( handler = this.handlers [ identifier.data ] ) || ! ( element = $ ( selector ) ).length )
			{
				return false;
			}

			new this.instance ( handler, channel, element );

			return true;
		},

		/*
		 * Close function
		 *
		 * @return null
		 */
		close: function ( )
		{
			return this.close ( );
		},

		/*
		 * Connect function
		 *
		 * @return integer|false
		 */
		connect: function ( receive, user, send, delay )
		{
			// Declare variables.
			var handler;

			if ( this.locked )
			{
				return false;
			}

			handler = new this.handler ( receive, user, send, delay );

			return handler.identifier.external;
		},

		/*
		 * Lock function
		 *
		 * @return boolean
		 */
		lock: function ( )
		{
			if ( this.locked )
			{
				return false;
			}

			this.locked = true;

			return true;
		}
	};

	// @todo: Better BBCode parser.

	// -----------------------------------------------------------------------
	// Copyright (c) 2008, Stone Steps Inc.
	// All rights reserved
	// http://www.stonesteps.ca/legal/bsd-license/
	//
	// This is a BBCode parser written in JavaScript. The parser is intended
	// to demonstrate how to parse text containing BBCode tags in one pass
	// using regular expressions.
	//
	// The parser may be used as a backend component in ASP or in the browser,
	// after the text containing BBCode tags has been served to the client.
	//
	// Following BBCode expressions are recognized:
	//
	// [b]bold[/b]
	// [i]italic[/i]
	// [u]underlined[/u]
	// [s]strike-through[/s]
	// [samp]sample[/samp]
	//
	// [color=red]red[/color]
	// [color=#FF0000]red[/color]
	// [size=1.2]1.2em[/size]
	//
	// [url]http://blogs.stonesteps.ca/showpost.asp?pid=33[/url]
	// [url=http://blogs.stonesteps.ca/showpost.asp?pid=33][b]BBCode[/b] Parser[/url]
	//
	// [q=http://blogs.stonesteps.ca/showpost.asp?pid=33]inline quote[/q]
	// [q]inline quote[/q]
	// [blockquote=http://blogs.stonesteps.ca/showpost.asp?pid=33]block quote[/blockquote]
	// [blockquote]block quote[/blockquote]
	//
	// [pre]formatted
	//     text[/pre]
	// [code]if(a == b)
	//   print('done');[/code]
	//
	// text containing [noparse] [brackets][/noparse]
	//
	// -----------------------------------------------------------------------
	var opentags;           // open tag stack
	var crlf2br = true;     // convert CRLF to <br>?
	var noparse = false;    // ignore BBCode tags?
	var urlstart = -1;      // beginning of the URL if zero or greater (ignored if -1)

	// aceptable BBcode tags, optionally prefixed with a slash
	var tagname_re = /^\/?(?:b|i|u|pre|samp|code|colou?r|size|noparse|url|s|q|blockquote|img)$/;

	// color names or hex color
	var color_re = /^(:?black|silver|gray|white|maroon|red|purple|fuchsia|green|lime|olive|yellow|navy|blue|teal|aqua|#(?:[0-9a-f]{3})?[0-9a-f]{3})$/i;

	// numbers
	var number_re = /^[\\.0-9]{1,8}$/i;

	// reserved, unreserved, escaped and alpha-numeric [RFC2396]
	var uri_re = /^[-;\/\?:@&=\+\$,_\.!~\*'\(\)%0-9a-z]{1,512}$/i;

	// main regular expression: CRLF, [tag=option], [tag] or [/tag]
	var postfmt_re = /([\r\n])|(?:\[([a-z]{1,16})(?:=([^\x00-\x1F"'\(\)<>\[\]]{1,256}))?\])|(?:\[\/([a-z]{1,16})\])/ig;

	// stack frame object
	function taginfo_t(bbtag, etag)
	{
	   this.bbtag = bbtag;
	   this.etag = etag;
	}

	// check if it's a valid BBCode tag
	function isValidTag(str)
	{
	   if(!str || !str.length)
		  return false;

	   return tagname_re.test(str);
	}

	//
	// m1 - CR or LF
	// m2 - the tag of the [tag=option] expression
	// m3 - the option of the [tag=option] expression
	// m4 - the end tag of the [/tag] expression
	//
	function textToHtmlCB(mstr, m1, m2, m3, m4, offset, string)
	{
	   //
	   // CR LF sequences
	   //
	   if(m1 && m1.length) {
		  if(!crlf2br)
			 return mstr;

		  switch (m1) {
			 case '\r':
				return '';
			 case '\n':
				return '<br>';
		  }
	   }

	   //
	   // handle start tags
	   //
	   if(isValidTag(m2)) {
		  // if in the noparse state, just echo the tag
		  if(noparse)
			 return '[' + m2 + ']';

		  // ignore any tags if there's an open option-less [url] tag
		  if(opentags.length && opentags[opentags.length-1].bbtag == 'url' && urlstart >= 0)
			 return '[' + m2 + ']';

		  switch (m2) {
			 case 'code':
				opentags.push(new taginfo_t(m2, '</code></pre>'));
				crlf2br = false;
				return '<pre><code>';

			 case 'pre':
				opentags.push(new taginfo_t(m2, '</pre>'));
				crlf2br = false;
				return '<pre>';

			 case 'color':
			 case 'colour':
				if(!m3 || !color_re.test(m3))
				   m3 = 'inherit';
				opentags.push(new taginfo_t(m2, '</span>'));
				return '<span style="color: ' + m3 + '">';

			 case 'size':
				if(!m3 || !number_re.test(m3))
				   m3 = '1';
				opentags.push(new taginfo_t(m2, '</span>'));
				return '<span style="font-size: ' + Math.min(Math.max(m3, 0.7), 3) + 'em">';

			 case 's':
				opentags.push(new taginfo_t(m2, '</span>'));
				return '<span style="text-decoration: line-through">';

			 case 'noparse':
				noparse = true;
				return '';

			 case 'url':
				opentags.push(new taginfo_t(m2, '</a>'));

				// check if there's a valid option
				if(m3 && uri_re.test(m3)) {
				   // if there is, output a complete start anchor tag
				   urlstart = -1;
				   return '<a href="' + m3 + '">';
				}

				// otherwise, remember the URL offset
				urlstart = mstr.length + offset;

				// and treat the text following [url] as a URL
				return '<a href="';

			case 'img':
				opentags.push(new taginfo_t(m2, '" />'));
				return '<img alt="" src="';

			 case 'q':
			 case 'blockquote':
				opentags.push(new taginfo_t(m2, '</' + m2 + '>'));
				return m3 && m3.length && uri_re.test(m3) ? '<' + m2 + ' cite="' + m3 + '">' : '<' + m2 + '>';

			 default:
				// [samp], [b], [i] and [u] don't need special processing
				opentags.push(new taginfo_t(m2, '</' + m2 + '>'));
				return '<' + m2 + '>';

		  }
	   }

	   //
	   // process end tags
	   //
	   if(isValidTag(m4)) {
		  if(noparse) {
			 // if it's the closing noparse tag, flip the noparse state
			 if(m4 == 'noparse')  {
				noparse = false;
				return '';
			 }

			 // otherwise just output the original text
			 return '[/' + m4 + ']';
		  }

		  // highlight mismatched end tags
		  if(!opentags.length || opentags[opentags.length-1].bbtag != m4)
			 return '<span style="color: red">[/' + m4 + ']</span>';

		  if(m4 == 'url') {
			 // if there was no option, use the content of the [url] tag
			 if(urlstart > 0)
				return '">' + string.substr(urlstart, offset-urlstart) + opentags.pop().etag;

			 // otherwise just close the tag
			 return opentags.pop().etag;
		  }
		  else if(m4 == 'code' || m4 == 'pre')
			 crlf2br = true;

		  // other tags require no special processing, just output the end tag
		  return opentags.pop().etag;
	   }

	   return mstr;
	}

	//
	// post must be HTML-encoded
	//
	function parseBBCode(post)
	{
	   var result, endtags, tag;

	   // convert CRLF to <br> by default
	   crlf2br = true;

	   // create a new array for open tags
	   if(opentags == null || opentags.length)
		  opentags = new Array(0);

	   // run the text through main regular expression matcher
	   result = post.replace(postfmt_re, textToHtmlCB);

	   // reset noparse, if it was unbalanced
	   if(noparse)
		  noparse = false;

	   // if there are any unbalanced tags, make sure to close them
	   if(opentags.length) {
		  endtags = new String();

		  // if there's an open [url] at the top, close it
		  if(opentags[opentags.length-1].bbtag == 'url') {
			 opentags.pop();
			 endtags += '">' + post.substr(urlstart, post.length-urlstart) + '</a>';
		  }

		  // close remaining open tags
		  while(opentags.length)
			 endtags += opentags.pop().etag;
	   }

	   return endtags ? result + endtags : result;
	}

	/*
	 * Master class
	 * @package Blink
	 *
	 * Singleton
	 */
	var blink = { };

	// Turn all external functions into a closure applied to our internal methods.
	for ( i in external )
	{
		blink [ i ] = ( function ( external, internal )
		{
			return function ( )
			{
				return external.apply ( internal, arguments );
			}
		} ) ( external [ i ], internal );
	}

	// Create new master class.
	window.blink = blink;
	window._blink = internal; // Debugging only.

	// Create a console for debugging.
	$ ( document ).ready ( function ( )
	{
		try
		{
			console.log ( 'blink: Blink Messenger loaded' );
		}
		catch ( exception )
		{
			console = {
				log: function ( )
				{
					// Do nothing.
					return null;
				}
			}
		}
	} );

	$ ( window ).unload ( ( function ( internal )
	{
		return function ( )
		{
			return internal.close ( );
		}
	} ) ( internal ) );
} ) ( jQuery );
